package com.sophon.andyj.batis;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;

import com.sophon.dao.CustomerMapper;
import com.sophon.vo.Customer;

public class MapperProxyFactoryAndyj {

	private Map<Class<?>, InvocationHandler> handlers;

	@SuppressWarnings("unchecked")
	public <T> T createMapperProxy(Class<T> mapper) {
		return (T) Proxy.newProxyInstance(mapper.getClassLoader(), new Class[] { mapper }, handlers.get(mapper));
	}

	/**
	 * Assume the mapper proxies should be initialized based on xml or mapper
	 * interfaces when server starting.
	 */
	public MapperProxyFactoryAndyj() {
		handlers = new HashMap<>();
		handlers.put(CustomerMapper.class, new InvocationHandler() {

			ExecutorAndyj executor = new ExecutorAndyj();

			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				try {
					if (Object.class.equals(method.getDeclaringClass())) {
						return method.invoke(this, args);
					} else if (isDefaultMethod(method)) {
						return invokeDefaultMethod(proxy, method, args);
					}
				} catch (Throwable t) {
					throw ExceptionUtil.unwrapThrowable(t);
				}
				Annotation[] annotations = method.getDeclaredAnnotations();
				Select select = (Select) annotations[0];
				ResultSet rs = executor.execute(select.value()[0]);
				List<Customer> customers = new ArrayList<>();
				Customer customer;
				while (rs.next()) {
					customer = new Customer();
					customer.setCustomerId(rs.getInt("customer_id"));
					customer.setCustomerName(rs.getString("customer_name"));
					customers.add(customer);
				}
				return customers;
			}

			/**
			 * Backport of java.lang.reflect.Method#isDefault()
			 */
			private boolean isDefaultMethod(Method method) {
				return (method.getModifiers()
						& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
						&& method.getDeclaringClass().isInterface();
			}

			@UsesJava7
			private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
				final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
						.getDeclaredConstructor(Class.class, int.class);
				if (!constructor.isAccessible()) {
					constructor.setAccessible(true);
				}
				final Class<?> declaringClass = method.getDeclaringClass();
				return constructor
						.newInstance(declaringClass,
								MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
										| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
						.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
			}
		});
	}
}
